package com.googlecode.messwertepi.services;

import com.google.inject.Inject;
import com.googlecode.messwertepi.configuration.Configuration;
import com.googlecode.messwertepi.gui.MainWindow;
import com.googlecode.messwertepi.model.DataModel;
import com.googlecode.messwertepi.module.GpioPinFactory;
import com.pi4j.io.gpio.GpioPinDigital;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.TooManyListenersException;

/**
 * Default implementation of the InputService.
 * This implementation uses pi4j to read the GPIO pins and RXTX to read the input data.
 * <p/>
 * Created by eike on 19.07.14.
 */
public class DefaultInputService implements InputService
{
    final private static Logger LOG = LoggerFactory.getLogger(DefaultInputService.class);

    final private Configuration configuration;
    final private GpioPinFactory gpioPinFactory;
    final private MainWindow mainWindow;
    final private SerialPort serialPort;
    final private DataModel model;

    @Inject
    public DefaultInputService(Configuration configuration, GpioPinFactory gpioPinFactory, MainWindow mainWindow, SerialPort serialPort, DataModel model)
    {
        this.configuration = configuration;
        this.gpioPinFactory = gpioPinFactory;
        this.mainWindow = mainWindow;
        this.serialPort = serialPort;
        this.model = model;

        initializeGpioPinListener();
        initializeSerialPortListener();
    }

    /**
     * This method initializes the gpio pin listener, that are
     * used to move the cursor up/down, confirm the input and print
     * the data.
     */
    final private void initializeGpioPinListener()
    {
        // initialize the down pin
        GpioPinDigital pinDown = gpioPinFactory.createInstance(configuration.getPinDown());
        pinDown.addListener(new GpioPinListenerDigital()
        {
            @Override
            public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event)
            {
                if (event.getState() == PinState.LOW) {
                    mainWindow.moveCursorDown();
                }
            }
        });

        // initialize the up pin
        GpioPinDigital pinUp = gpioPinFactory.createInstance(configuration.getPinUp());
        pinDown.addListener(new GpioPinListenerDigital()
        {
            @Override
            public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event)
            {
                if (event.getState() == PinState.LOW) {
                    mainWindow.moveCursorUp();
                }
            }
        });

        // initialize the confirm pin
        GpioPinDigital pinConfirm = gpioPinFactory.createInstance(configuration.getPinConfirm());
        pinDown.addListener(new GpioPinListenerDigital()
        {
            @Override
            public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event)
            {
                System.out.println("Confirm !!");
            }
        });

        // initialize the print pin
        GpioPinDigital pinPrint = gpioPinFactory.createInstance(configuration.getPinPrint());
        pinDown.addListener(new GpioPinListenerDigital()
        {
            @Override
            public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event)
            {
                System.out.println("Print !!");
            }
        });
    }

    /**
     * Initializes the serial port listener.
     */
    final private void initializeSerialPortListener()
    {
        // get the streams
        try {
            final InputStream in = serialPort.getInputStream();

            serialPort.addEventListener(new SerialPortEventListener()
            {
                @Override
                public void serialEvent(SerialPortEvent serialPortEvent)
                {
                    if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
                        byte[] buffer = new byte[1024];
                        int bytesRead = 0;
                        String input = "";
                        try {
                            while ((bytesRead = in.read(buffer)) != -1) {
                                input += new String(buffer, 0, bytesRead);
                            }

                            LOG.debug("Data read: " + input);
                            model.setInput(input);
                        } catch (IOException ioe) {
                            LOG.error("Failed to read data from the serial port", ioe);
                        }
                    }
                }
            });
        } catch (IOException ioe) {
            LOG.error("Failed to get serial port input stream.", ioe);
        } catch (TooManyListenersException tmle) {
            LOG.error("Unable to add the serial port event listener.", tmle);
        }
    }

}
